package Definitions;
import java.util.ArrayList;

import Global.GlobalClass;
import GraphProperties.PropertiesOfGraphClass;

/*****************************************************************
* Name : Graph
* Aim : This class aims to keep the data of the whole dataSet in a graph structure 
* that contains a NodeList
*****************************************************************/
public class GraphClass implements Cloneable
{
	/*dataSet to keep the nodeList(the whole dataset which is read from external files)*/
	ArrayList<NodeClass> dataSet;
	
	ArrayList<PropertiesOfGraphClass> propertiesOfTheGraphWithRespectToEdgeType;

	/*Constructor of the class*/
	public GraphClass() 
	{
		this.dataSet = new ArrayList<NodeClass>();
		this.propertiesOfTheGraphWithRespectToEdgeType = new ArrayList<PropertiesOfGraphClass>();
	}

	public ArrayList<NodeClass> getDataSet()
	{
		return dataSet;
	}

	public void setDataSet(ArrayList<NodeClass> dataSet) 
	{
		this.dataSet = dataSet;
	}
	
	public ArrayList<PropertiesOfGraphClass> getPropertiesOfTheGraphWithRespectToEdgeType()
	{
		return propertiesOfTheGraphWithRespectToEdgeType;
	}
	
	public void setPropertiesOfTheGraphWithRespectToEdgeType(ArrayList<PropertiesOfGraphClass> propertiesOfTheGraphWithRespectToEdgeType)
	{
		this.propertiesOfTheGraphWithRespectToEdgeType = propertiesOfTheGraphWithRespectToEdgeType;
	}
	
	
	/*****************************************************************
	* Name : addNeighbourNode
	* Aim : This method aims to assign the neighborhood relationship between two given nodes neighbour1 and neighbour2
	* it is used in the read operation while reading from the cites file.
	* yet, in the whole dataSet and is used especially in the Read operation from file.
	* Inputs : String neighbour1, String neighbour2, int edgeType *
	* Outputs :  void *
	* Data Structures : *
	* Algorithms : Firstly, this method checks the all dataset and find the corresponding nodes with the parameters of the method 
	* and change their neighborlist. *
	*****************************************************************/
	public void addNeighbourNode(String neighbour1, String neighbour2, int edgeType, GlobalClass global) 
	{
		int order1 = -1, order2 = -1;
		int k = 0;
		//edgeType = 0;	//For test and since there are no multilinks for cora the edgeType is 0
		
		/*Traverse whole dataSet(graph) and find the related orders of the nodes with neigbour1 and neighbour2*/
		for (int i = 0; i < dataSet.size() && k < 2; i++) 
		{
			String id = dataSet.get(i).getId();
			if (id.compareTo(neighbour1) == 0)
			{
				order1 = i;
				k++;
			}
			if (id.compareTo(neighbour2) == 0) 
			{
				k++;
				order2 = i;
			}
		}
		/*If there are no nodes corresponding with the given orders*/
		if (order1 == -1 && order2 == -1) 
		{
			return;
		}
		else if (order1 == -1)
			System.out.println("HATALI DURUM 1: order1: "+ order1 +" order2: "+order2 +" neighbour1: "+neighbour1 +" neighbour2: "+neighbour2);
		else if (order2 == -1)
			System.out.println("HATALI DURUM 2: order1: "+ order1 +" order2: "+order2 +" neighbour1: "+neighbour1 +" neighbour2: "+neighbour2);
		
		if (order1 == -1 || order2 == -1) 
		{
			global.linksChecker.increaseNumberOfLinksForNonExistingNodes();
			return;
		}
		
		//TODO AS KONTROL HEM YUKARIYI HEM DE ASAGIYI.
		
		try
		{
			if(true)//Parametres.getParallelLinksAllowed() == false)
			{			
				if(NodeClass.isThereAnyEdge(dataSet.get(order1), dataSet.get(order2), edgeType)==true)
					//out.write(order1+" "+order2+"\n");
				//else
				{	
					global.linksChecker.increaseNumberOfParallelLinks();					
				}
			}
			//else
			{
				//if(outForParallel!=null)
					//outForParallel.write(order1+" "+order2+"\n");
			}				
		}
		catch(Exception e)
		{
			e.printStackTrace();
			System.out.println(order1+" "+order2+"\n");
			System.exit(0);
		}


		/*Add new neighbours of the nodes to their neighbour list with the corresponding edgeType*/
		
		/*degisebilir*/
	
		int i=0;
		for(; i<this.dataSet.get(order1).getEdgeList().get(edgeType).getNeigbours().size(); i++)
			if(this.dataSet.get(order1).getEdgeList().get(edgeType).getNeigbours().get(i).getId().compareTo(neighbour2)==0)
				break;
		if(i==this.dataSet.get(order1).getEdgeList().get(edgeType).getNeigbours().size())
			this.dataSet.get(order1).getEdgeList().get(edgeType).getNeigbours().add(this.dataSet.get(order2));
		
		for(i=0; i<this.dataSet.get(order2).getEdgeList().get(edgeType).getNeigbours().size(); i++)
			if(this.dataSet.get(order2).getEdgeList().get(edgeType).getNeigbours().get(i).getId().compareTo(neighbour1)==0)
				break;
		if(i==this.dataSet.get(order2).getEdgeList().get(edgeType).getNeigbours().size())
		this.dataSet.get(order2).getEdgeList().get(edgeType).getNeigbours().add(this.dataSet.get(order1));
	}
		/*****************************************************************
	* Name : InitializeEdgeList
	* Aim : This method aims to create a new neighborlist that means another edgeType and is used in Read operation. If there is one link type,
	* then this method will be called for one time.
	* Inputs : int edgeType *
	* Outputs :  - *
	* Data Structures : *
	* Algorithms : *
	*****************************************************************/
	public void InitializeEdgeList(int edgeType)
	{
		for(int i=0; i< dataSet.size(); i++)
		{
			EdgeClass e= new EdgeClass();
			e.setId(edgeType);
			dataSet.get(i).getEdgeList().add(e);
		}
		propertiesOfTheGraphWithRespectToEdgeType.add(new PropertiesOfGraphClass(this, edgeType));			
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
	
		for(int i=0; i<dataSet.size(); i++)
		{
			System.out.println(dataSet.get(i).toString());
		}
		return "";
	}
	public void print(int edgeType)
	{
		System.out.print("\n");
		for(int i=0; i<dataSet.size(); i++)
		{
			dataSet.get(i).print(edgeType);
		}
	}
	
	/*****************************************************************
	* Name : Sampling
	* Aim : This method aims to call the related sampling method that corresponds to user's choice
	* Inputs : int i, double testRatio, double valRatio, double threshold, int edgeType *
	* Outputs :  - *
	* Data Structures : *
	* Algorithms : for i = 0, it calls Random Sampling, for i = 1 it calls Snowball Sampling 
	 * @throws Exception *
	*****************************************************************/

	/*****************************************************************
	* Name : getValidationError
	* Aim : This method aims to return the error rate for the label assignments to validation nodes.
	* Inputs : - *
	* Outputs :  double *
	* Data Structures : *
	* Algorithms : - *
	*****************************************************************/
	/*
	public double getValidationError() 
	{
		int valNode = 0;
		int validAssign = 0;
		double k;
		for (int j = 0; j < dataSet.size(); j++) 
		{
			if (dataSet.get(j).isTrain() && dataSet.get(j).isVal()) 
			{
				valNode++;
				if (dataSet.get(j).getClassOrder() == dataSet.get(j).getPredictClassOrder())
				{
					validAssign++;	
				}
			}
		}
		if (valNode == 1)
		{
			return 0;
		}

		k = Double.parseDouble(Integer.toString(validAssign))
				/ Double.parseDouble(Integer.toString(valNode));
		return k;
	}*/
	

	
	/*****************************************************************
	* Function Name:	combineArrays
	* Aim: 				combine to arrays each other
	* Inputs: 			ArrayList <Integer> arrayList2: first List
	* 					ArrayList <Integer> arrayList2: secondList
	* Outputs:			---
	* Data Structures: 	---
	* Algorithms: 		---
	*****************************************************************/
	public ArrayList<Double> combineArrays(ArrayList<Double> firstList,ArrayList<Double> secondList)
	{
		ArrayList<Double> resultOfCombination = new ArrayList<Double>();

		for (int i = 0; i < firstList.size(); i++)
			resultOfCombination.add(firstList.get(i));
		for (int i = 0; i < secondList.size(); i++)
			resultOfCombination.add(secondList.get(i));

		return resultOfCombination;
	}
	
	/*****************************************************************
	* Function Name:	findSimilarities
	* Aim: 				find similarities between two Node
	* Inputs: 			Node first
	* 					Node second
	* Outputs:			double similarityes
	* Data Structures: 	---
	* Algorithms: 		---
	*****************************************************************/
	public double findSmilarities(int  contentType, NodeClass u, NodeClass v )
	{
		double similarity;
		int lenght = u.getContentList().get(contentType).getAttributeList().size();
		
		double lenghtOfu = 0;
		double lenghtOfv = 0;
		double sum=0;
		
		for(int i= 0; i< lenght; i++)
		{
			sum += (u.getContentList().get(contentType).getAttributeList().get(i)* 
					v.getContentList().get(contentType).getAttributeList().get(i));
			
			lenghtOfu += (u.getContentList().get(contentType).getAttributeList().get(i)*
					u.getContentList().get(contentType).getAttributeList().get(i));
			
			lenghtOfv += (v.getContentList().get(contentType).getAttributeList().get(i)*
					v.getContentList().get(contentType).getAttributeList().get(i));
		}
			
		lenghtOfu = Math.sqrt(lenghtOfu);
		lenghtOfv = Math.sqrt(lenghtOfv);
		
		similarity= sum / (lenghtOfu * lenghtOfv); 
		return similarity;
	}
	
	/*****************************************************************
	* Function Name:	findDegreeOfEdges
	* Aim: 				find similarities between two Node
	* Inputs: 			int selectedEdges
	* Outputs:			double 
	* Data Structures: 	---
	* Algorithms: 		---
	*****************************************************************/
	public double findDegreeOfEdges(int selectedEdges)
	{
		double sum=0;
		for(int i=0; i< dataSet.size(); i++)
			sum+= dataSet.get(i).getEdgeList().get(selectedEdges).getNeigbours().size();
		sum=  sum / (double)dataSet.size(); 
		return sum;
	}
	
	
	/*****************************************************************
	* Name : addNeighbourNode
	* Aim : This method aims to assign thhe neighborhood relationship between two given nodes neighbour1 and neighbour2
	* it is used in the read operation while reading from the cites file.
	* yet, in the whole dataSet and is used especially in the Read operation from file.
	* Inputs : String neighbour1, String neighbour2, int edgeType *
	* Outputs :  void *
	* Data Structures : *
	* Algorithms : Firstly, this method checks the all dataset and find the corresponding nodes with the parameters of the method 
	* and change their neighborlist. *
	*****************************************************************/
	public void addNeighbourNode(NodeClass neighbour1, NodeClass neighbour2, int edgeType) 
	{
		
		int i=0;
		for(; i< neighbour1.getEdgeList().get(edgeType).getNeigbours().size(); i++ )
		{
			if(neighbour1.getEdgeList().get(edgeType).getNeigbours().get(i).getId()==neighbour2.getId())
				break;
		}
		if(i==neighbour1.getEdgeList().get(edgeType).getNeigbours().size())
			{
				neighbour1.getEdgeList().get(edgeType).getNeigbours().add(neighbour2);
				//System.out.println("1. 2.ye : "+ neighbour1.getId()+"  -- "+ neighbour2.getId());
			}
		for(i=0; i< neighbour2.getEdgeList().get(edgeType).getNeigbours().size(); i++ )
		{
			if(neighbour2.getEdgeList().get(edgeType).getNeigbours().get(i).getId()==neighbour1.getId())
				break;
		}
		if(i==neighbour2.getEdgeList().get(edgeType).getNeigbours().size())
			{
				neighbour2.getEdgeList().get(edgeType).getNeigbours().add(neighbour1);
				//System.out.println("1. 2.ye : "+ neighbour1.getId()+"  -- "+ neighbour2.getId());
			}
	}
	
		/*Graph akoy*/
	public void defineContentNeighborsOfGraphGivenContentAndSpecifiedThreshold(int contentType, double threshold)
	{
		int size=this.getDataSet().size(); 
		
		for(int i=0; i<size ; i++)
		{
			NodeClass u= this.getDataSet().get(i);
			
			for(int j=i+1; j<size ; j++)
			{
				NodeClass v= this.getDataSet().get(j);
				/* check v is neigbor */
				if(u.IsGivenNodeSimilarNeighbourForGivenContentAndThreshold(v,contentType, threshold))
				{
					u.getContentList().get(contentType).contentNeighborList.add(v);
					v.getContentList().get(contentType).contentNeighborList.add(u);
					System.out.println("i "+ i+  " is similar to " +j );
				}
			}
		}
	}

	
	/*****************************************************************
	* Name : addNearestDefinedNodesAsNewEdgeType
	* Aim : To add neighborhood relationship between the nodes that has highest similarity with 
	* respect to maximum node degree.
	* Inputs : int degree: how many nodes will be added as neighbor for each node
	* 			int contentType: which contenttype will  be used 
	* 			int edgeType : which edgetype will  be used 
	* Outputs :  void *
	* Data Structures : *
	* Algorithms : After this method is processed, Gx graph is ready to use.
	*****************************************************************/
	public void addNearestDefinedNodesAsNewEdgeType(int degree, int contentType, int edgeType)
	{
		int sizeOfDataset = this.dataSet.size();
		int sizeOfContents = this.dataSet.get(0).getContentList().get(0).getAttributeList().size();
		double result;
		double [][] resultOfSimilarity= new double[sizeOfDataset][sizeOfDataset];
		System.out.println("size. " + sizeOfContents + " " + sizeOfDataset);
		
		//The similarity matrix is constructed here.
		for(int i = 0; i < sizeOfDataset; i++)
		{		
			for(int j = 0; j < sizeOfDataset; j++)
			{
				result= findSmilarities(contentType, dataSet.get(j), dataSet.get(i));
				resultOfSimilarity[i][j]= result;
			}			
		}
	
		//Initialize the new edgeType
		int newEdgeTypeID= dataSet.get(0).getEdgeList().size();
		InitializeEdgeList(newEdgeTypeID);
		
		 
		//Choose the highest values of each row and assign these node's as neighbors to each other.
		for(int i = 0; i < sizeOfDataset; i++)
		{		
			int findClosest=0;
			while(findClosest!= degree )
			{
						int maxOrder= 0;
						double maxSimilarity=resultOfSimilarity[i][0];
						double tempSimilarity;
						for(int j = 1; j < sizeOfDataset; j++)
						{
							tempSimilarity= resultOfSimilarity[i][j];
							if(tempSimilarity>maxSimilarity)
							{
								maxSimilarity=tempSimilarity;
								maxOrder= j;
							}
						}
						resultOfSimilarity[i][maxOrder]=0;
						addNeighbourNode( dataSet.get(i),dataSet.get(maxOrder), newEdgeTypeID);
						findClosest++;
			}
		}
}

	
	public void ClearAllGraph() 
	{
		dataSet.clear();		
	}
	
	
	public Object clone()
	{
		GraphClass another = null;
		try
		{
			another = (GraphClass) super.clone();
			// shallow copy made so far. Now we will make it deep
			another.dataSet = (ArrayList<NodeClass>) dataSet.clone();
			
			another.dataSet = new ArrayList<NodeClass>();
			
			for(int i=0; i<dataSet.size(); i++)
			{
				another.dataSet.add((NodeClass)dataSet.get(i).clone());
			}
		}
		catch(CloneNotSupportedException e) {e.printStackTrace();} 
		//This exception will not occur
	
		return another;
	}
}






	


